/*
 * Copyright (c) 2022 PATEO CONNECT+ (Nanjing) Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "voice_assistant_ability_stub.h"
#include "common_utils.h"
#include "nlohmann/json.hpp"
#include "voice_assistant_log.h"
#include "voice_cloud_loader.h"
#include "wakeup_manager.h"
#include <exception>
#include <thread>

using namespace OHOS::CarVoiceAssistant::CommonUtils;

namespace OHOS {
namespace CarVoiceAssistant {

#define WRITE_PARCEL_WITH_RET(parcel, type, data, retval)                        \
    do {                                                                         \
        if (!(parcel).Write##type(data)) {                                       \
            VOICE_ASSISTANT_LOGI("%{public}s write " #data " failed", __func__); \
            return (retval);                                                     \
        }                                                                        \
    } while (0)

#define READ_PARCEL_WITH_RET(parcel, type, out, retval)                        \
    do {                                                                       \
        if (!(parcel).Read##type(out)) {                                       \
            VOICE_ASSISTANT_LOGI("%{public}s read " #out " failed", __func__); \
            return (retval);                                                   \
        }                                                                      \
    } while (0)

    VoiceAssistantAbilityAgentStub::VoiceAssistantAbilityAgentStub()
    {
        isWakeUpEnabled_ = false;
        isRecognizing_ = false;
        coord_ = std::make_pair(31.32751, 118.8921);
        hotwords_ = "";
        // startRecognizingTime_ = 0;
        player_ = nullptr;

        callbackEventTarget_ = new VoiceAssistantCallbackEventTarget();

        voiceCloudManager_ = CreateVoiceCloudManager();
        if (voiceCloudManager_) {
            voiceCloudManager_->SetCallback(static_cast<IVoiceCloudManagerCallback*>(this));
        }

        audioRecordManager_ = new AudioRecordManager();
        audioRecordManager_->SetCallback(static_cast<IAudioRecordCallback*>(this));

        ttsManager_ = new TTSManager();
        ttsManager_->SetCallback(static_cast<ITTSManagerCallback*>(this));
        ttsManager_->voiceCloudManager_ = voiceCloudManager_;

        wakeUpManager_ = new WakeUpManager();
        wakeUpManager_->SetCallback(static_cast<IWakeUpCallback*>(this));
        wakeUpManager_->Init();
    }

    VoiceAssistantAbilityAgentStub::~VoiceAssistantAbilityAgentStub()
    {
        callbackEventTarget_ = nullptr;
        if (voiceCloudManager_) {
            DestoryVoiceCloudManager(voiceCloudManager_);
            voiceCloudManager_ = nullptr;
        }
        audioRecordManager_ = nullptr;
        ttsManager_ = nullptr;
        if (wakeUpManager_) {
            delete wakeUpManager_;
            wakeUpManager_ = nullptr;
        }
    }

    int32_t VoiceAssistantAbilityAgentStub::OnRemoteRequest(uint32_t code,
        MessageParcel& data,
        MessageParcel& reply,
        MessageOption& option)
    {
        VOICE_ASSISTANT_LOGI("VoiceAssistantAbilityAgentStub::OnRemoteRequest code = %{public}u, pid=%{public}lu", code, pthread_self());
        switch (code) {
        case VOICE_ASSITANT_CMD_IS_ENABLE_WAKEUP: {
            bool isEnableWakeUp = false;
            IsEnableWakeUp(isEnableWakeUp);
            WRITE_PARCEL_WITH_RET(reply, Bool, isEnableWakeUp, VOICE_ASSISTANT_ERR);
        } break;
        case VOICE_ASSITANT_CMD_ENABLE_WAKEUP:
            EnableWakeUp();
            break;
        case VOICE_ASSITANT_CMD_DISABLE_WAKEUP:
            DisableWakeUp();
            break;
        case VOICE_ASSITANT_CMD_IS_RECOGNIZING: {
            bool isRecognizing;
            IsRecognizing(isRecognizing);
            WRITE_PARCEL_WITH_RET(reply, Bool, isRecognizing, VOICE_ASSISTANT_ERR);
        } break;
        case VOICE_ASSITANT_CMD_START_RECOGNIZE: {
            VoiceAssistantErrorCode result = VOICE_ASSISTANT_OK;
            StartRecognize(result);
            WRITE_PARCEL_WITH_RET(reply, Int32, result, VOICE_ASSISTANT_ERR);

        } break;
        case VOICE_ASSITANT_CMD_STOP_RECOGNIZE:
            StopRecognize();
            break;
        case VOICE_ASSITANT_CMD_PLAY_TTS: {
            std::string tts;
            VoiceAssistantErrorCode result = VOICE_ASSISTANT_OK;
            READ_PARCEL_WITH_RET(data, String, tts, VOICE_ASSISTANT_ERR);
            PlayTTS(result, tts);
            WRITE_PARCEL_WITH_RET(reply, Int32, result, VOICE_ASSISTANT_ERR);

        } break;
        case VOICE_ASSITANT_CMD_STOP_PLAY_TTS:
            StopPlayTTS();
            break;
        case VOICE_ASSITANT_CMD_REGISTER_HOTWORDS: {
            std::string hotwords;
            READ_PARCEL_WITH_RET(data, String, hotwords, VOICE_ASSISTANT_ERR);
            RegisterHotwords(hotwords);
        } break;
        case VOICE_ASSISTANT_CMD_SET_COORD: {
            double latitude;
            double longitude;
            READ_PARCEL_WITH_RET(data, Double, latitude, VOICE_ASSISTANT_ERR);
            READ_PARCEL_WITH_RET(data, Double, longitude, VOICE_ASSISTANT_ERR);
            SetCoord(latitude, longitude);
        } break;
        case VOICE_ASSITANT_CMD_REGISTER_CALLBACK: {
            sptr<IVoiceAssistantClientCallback> proxy = iface_cast<IVoiceAssistantClientCallback>(data.ReadRemoteObject());
            if (!proxy) {
                VOICE_ASSISTANT_LOGI("OnRemoteRequest-VOICE_ASSITANT_CMD_REGISTER_CALLBACK: is null");
                return 0;
            }
            sptr<VoiceAssistantClientCallbackDeathRecipient> deathRecipient = new VoiceAssistantClientCallbackDeathRecipient();
            deathRecipient->SetNotifyCb(std::bind(&VoiceAssistantAbilityAgentStub::RemoveCallback, this, std::placeholders::_1));
            proxy->AsObject()->AddDeathRecipient(deathRecipient);
            callbackEventTarget_->AddListener(proxy, deathRecipient);
        } break;
        case VOICE_ASSITANT_CMD_CHANGE_SPEAKER_TYPE: {
            std::string speakerType;
            READ_PARCEL_WITH_RET(data, String, speakerType, VOICE_ASSISTANT_ERR);
            ChangeSpeakerType(speakerType);
        } break;
        default:
            break;
        }
        return 0;
    }

    int32_t VoiceAssistantAbilityAgentStub::IsEnableWakeUp(bool& isEnable)
    {
        VOICE_ASSISTANT_LOGI("IsEnableWakeUp:%{public}s", isWakeUpEnabled_ ? "true" : "false");
        std::lock_guard<std::mutex> lock(mutex_);
        isEnable = isWakeUpEnabled_;
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::EnableWakeUp()
    {
        VOICE_ASSISTANT_LOGI("EnableWakeUp");
        std::lock_guard<std::mutex> lock(mutex_);
        if (isWakeUpEnabled_) {
            VOICE_ASSISTANT_LOGI("EnableWakeUp: is already enabled");
            return VOICE_ASSISTANT_OK;
        }
        if (audioRecordManager_->GetStatus() != AudioRecordStatusNone) {
            isWakeUpEnabled_ = true;
            VOICE_ASSISTANT_LOGI("EnableWakeUp: recording is running");
            return VOICE_ASSISTANT_OK;
        }
        VOICE_ASSISTANT_LOGI("EnableWakeUp: start record");
        bool rst = audioRecordManager_->StartRecord();
        if (rst) {
            VOICE_ASSISTANT_LOGI("EnableWakeUp: start record success");
            isWakeUpEnabled_ = true;
        } else {
            VOICE_ASSISTANT_LOGI("EnableWakeUp: start record failed");
        }

        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::DisableWakeUp()
    {
        VOICE_ASSISTANT_LOGI("DisableWakeUp");
        std::lock_guard<std::mutex> lock(mutex_);
        if (!isWakeUpEnabled_) {
            VOICE_ASSISTANT_LOGI("DisableWakeUp: is already disabled");
            return VOICE_ASSISTANT_OK;
        }

        if (audioRecordManager_->GetStatus() == AudioRecordStatusNone) {
            isWakeUpEnabled_ = false;
            VOICE_ASSISTANT_LOGI("DisableWakeUp: audio is not recording");
            return VOICE_ASSISTANT_OK;
        }

        if (isRecognizing_) {
            VOICE_ASSISTANT_LOGI("DisableWakeUp: isRecognizing, do not stop recording");
            isWakeUpEnabled_ = false;
            return VOICE_ASSISTANT_OK;
        }

        VOICE_ASSISTANT_LOGI("DisableWakeUp: stop recording");
        audioRecordManager_->StopRecord();
        isWakeUpEnabled_ = false;
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::IsRecognizing(bool& isRecognizing)
    {
        VOICE_ASSISTANT_LOGI("IsRecognizing:%{public}s", isRecognizing_ ? "true" : "false");
        std::lock_guard<std::mutex> lock(mutex_);
        isRecognizing = isRecognizing_;
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::StartRecognize(CommonUtils::VoiceAssistantErrorCode& result)
    {
        VOICE_ASSISTANT_LOGI("StartRecognize");
        std::lock_guard<std::mutex> lock(mutex_);

        if (isRecognizing_) {
            VOICE_ASSISTANT_LOGI("StopRecognize: is already  recognizing");
            result = VOICE_ASSISTANT_OK;
            return VOICE_ASSISTANT_OK;
        }

        ttsManager_->CancelAll();

        if (audioRecordManager_->GetStatus() == AudioRecordStatusNone) {
            bool rst = audioRecordManager_->StartRecord();
            if (rst) {
                VOICE_ASSISTANT_LOGI("StartRecognize: start record success");
            } else {
                VOICE_ASSISTANT_LOGI("StartRecognize: start record failed");
                result = VOICE_ASSISTANT_START_RECORD_FAILED;
                return VOICE_ASSISTANT_ERR;
            }
        }

        //启动websocket
        bool rst = ConnectWebsocket();
        if (!rst) {
            VOICE_ASSISTANT_LOGI("StartRecognize: connect websocket failed");
            result = VOICE_ASSISTANT_START_WEBSOCKET_CONNECT_FAILED;
            return VOICE_ASSISTANT_ERR;
        }

        PlayStartRecoginizingSound();
        isRecognizing_ = true;
        // startRecognizingTime_ = CommonUtils::GetTimestamp();

        callbackEventTarget_->EmitRecognizeStateChanged(isRecognizing_);

        std::thread startAudioStreamThread(&VoiceAssistantAbilityAgentStub::SendStartAudioStreamIfNeeded, this);
        startAudioStreamThread.detach();

        result = VOICE_ASSISTANT_OK;

        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::StopRecognize()
    {
        VOICE_ASSISTANT_LOGI("StopRecognize");
        std::lock_guard<std::mutex> lock(mutex_);
        if (!isRecognizing_) {
            VOICE_ASSISTANT_LOGI("StopRecognize: is already not recognizing");
            return VOICE_ASSISTANT_OK;
        }

        if (!isWakeUpEnabled_ && audioRecordManager_->GetStatus() != AudioRecordStatusNone) {
            VOICE_ASSISTANT_LOGI("StopRecognize:stop record");
            audioRecordManager_->StopRecord();
        }

        voiceCloudManager_->SendEndAudioStream();

        wakeUpManager_->SetNeedClearBeforeProcess();
        isRecognizing_ = false;

        callbackEventTarget_->EmitRecognizeStateChanged(isRecognizing_);

        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::PlayTTS(CommonUtils::VoiceAssistantErrorCode& result, std::string& tts)
    {
        VOICE_ASSISTANT_LOGI("PlayTTS:%{public}s", tts.c_str());
        ttsManager_->RequestPlay(tts);
        result = VOICE_ASSISTANT_OK;
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::StopPlayTTS()
    {
        VOICE_ASSISTANT_LOGI("StopPlayTTS");
        ttsManager_->CancelAll();
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::RegisterHotwords(std::string& hotwords)
    {
        VOICE_ASSISTANT_LOGI("RegisterHotwords:%{public}s", hotwords.c_str());
        std::lock_guard<std::mutex> lock(mutex_);
        hotwords_ = hotwords;
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::SetCoord(double latitude, double longitude)
    {
        VOICE_ASSISTANT_LOGI("SetCoord:%{public}f,%{public}f", latitude, longitude);
        coord_ = std::make_pair(latitude, longitude);
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::RegisterCallback()
    {
        return VOICE_ASSISTANT_OK;
    }

    int32_t VoiceAssistantAbilityAgentStub::ChangeSpeakerType(std::string speakerType)
    {
        VOICE_ASSISTANT_LOGI("ChangeSpeakerType:%{public}s", speakerType.c_str());
        ttsManager_->ChangeSpeakerType(speakerType);
        return VOICE_ASSISTANT_OK;
    }

    void VoiceAssistantAbilityAgentStub::RemoveCallback(const wptr<IRemoteObject>& remoteObject)
    {
        callbackEventTarget_->RemoveListener(remoteObject);
    }

    bool VoiceAssistantAbilityAgentStub::ConnectWebsocket()
    {
        VOICE_ASSISTANT_LOGI("ConnectWebsocket");
        if (voiceCloudManager_->GetStatus() == VoiceCloudStatusConnected || voiceCloudManager_->GetStatus() == VoiceCloudStatusConnecting) {
            VOICE_ASSISTANT_LOGI("ConnectWebsocket: is connect or connectting, need not connect");
            return true;
        }

        return voiceCloudManager_->Connect();
    }

    void VoiceAssistantAbilityAgentStub::SendStartAudioStreamIfNeeded()
    {
        VOICE_ASSISTANT_LOGI("SendStartAudioStreamIfNeeded");
        usleep(1000000);

        if (!isRecognizing_) {
            VOICE_ASSISTANT_LOGI("SendStartAudioStreamIfNeeded: Is not recognizing, not need send start");
            return;
        }

        if (voiceCloudManager_->IsSendingAudioStream()) {
            VOICE_ASSISTANT_LOGI("SendStartAudioStreamIfNeeded: Is sending stream, not need send start");
            return;
        }
        if (voiceCloudManager_->GetStatus() == VoiceCloudStatusConnected) {
            voiceCloudManager_->SendTrackStat(coord_.first, coord_.second, hotwords_);
            voiceCloudManager_->SendStartAudioStream();
            VOICE_ASSISTANT_LOGI("SendStartAudioStreamIfNeeded: send start audio stream");
        }
    }

    void VoiceAssistantAbilityAgentStub::VoiceCloudStatusChanged(VoiceCloudStatus status)
    {
        switch (status) {
        case VoiceCloudStatusNone:
            if (isRecognizing_) {
                StopRecognize();
            }
            break;
        case VoiceCloudStatusConnected: {
            std::thread startAudioStreamThread(&VoiceAssistantAbilityAgentStub::SendStartAudioStreamIfNeeded, this);
            startAudioStreamThread.detach();
        } break;
        default:
            break;
        }
    }

    void VoiceAssistantAbilityAgentStub::ReveiceVoiceCloudMessage(void* data, size_t length, bool isBinary)
    {
        if (isBinary) {
            VOICE_ASSISTANT_LOGI("ReveiceVoiceCloudMessage: not need parse binary");
            return;
        }

        if (length == 0) {
            return;
        }

        std::string dataStr(static_cast<char*>(data), length);
        VOICE_ASSISTANT_LOGI("ReveiceVoiceCloudMessage:length:%{public}zu, %{public}s", length, dataStr.c_str());
        nlohmann::json json = nlohmann::json::parse(dataStr, nullptr, false);
        if (json.is_discarded()) {
            VOICE_ASSISTANT_LOGI("ReveiceVoiceCloudMessage: json parse failed");
            return;
        }

        std::string op = json.at("op");
        if (op == "stopListen") {
            StopRecognize();
        } else {
            if (op == "realTimeASRResult") {
                bool isFinish = json.at("isFinish");
                if (isFinish) {
                    StopRecognize();
                }
            }

            callbackEventTarget_->EmitAsrResult(dataStr);
        }
    }

    void VoiceAssistantAbilityAgentStub::AudioRecordStatusChanged(AudioRecordStatus status)
    {
        switch (status) {
        case AudioRecordStatusNone:
            if (isRecognizing_) {
                StopRecognize();
            }
            if (isWakeUpEnabled_) {
                DisableWakeUp();
            }
            break;
        default:
            break;
        }
    }

    void VoiceAssistantAbilityAgentStub::ReceiveAudioBuffer(void* data, size_t length)
    {
        SendAudioBufferToWebsocketIfNeeded(data, length);
        CheckWakeUpIfNeeded(data, length);
    }

    void VoiceAssistantAbilityAgentStub::WakeUpCallback(std::string text)
    {
        if (isRecognizing_ || !isWakeUpEnabled_) {
            return;
        }

        if (text == "你好博泰") {
            callbackEventTarget_->EmitOnWakeUp();
            CommonUtils::VoiceAssistantErrorCode result = VOICE_ASSISTANT_OK;
            StartRecognize(result);
        }
    }

    void VoiceAssistantAbilityAgentStub::AudioPlayerStatusChanged(bool isPlaying)
    {
        if (callbackEventTarget_ != nullptr) {
            callbackEventTarget_->EmitTTSPlayStateChanged(isPlaying);
        }
    }

    void VoiceAssistantAbilityAgentStub::SendAudioBufferToWebsocketIfNeeded(void* data, size_t length)
    {
        if (!isRecognizing_) {
            return;
        }

        if (!voiceCloudManager_->IsSendingAudioStream()) {
            return;
        }

        voiceCloudManager_->SendBinary(data, length);
    }

    void VoiceAssistantAbilityAgentStub::CheckWakeUpIfNeeded(void* data, size_t length)
    {
        if (!isWakeUpEnabled_) {
            return;
        }

        if (isRecognizing_) {
            return;
        }

        wakeUpManager_->Process(data, length);
    }

    void VoiceAssistantAbilityAgentStub::PlayStartRecoginizingSound()
    {
        if (player_ == nullptr) {
            player_ = OHOS::Media::PlayerFactory::CreatePlayer();
        }
        player_->SetSource("/system/etc/pocketsphinx/voice_tip.mp3");
        player_->Prepare();
        player_->Play();
    }
}
}
